package redis

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
	"reflect"
	"time"
	"xrt/constants"
	. "xrt/core"
	"xrt/utils"
	"xrt/utils/file/log"
	"xrt/utils/maps"
	mr "xrt/utils/reflect"
)

type redisLayer struct {
	rdb         *redis.Pool
	server      string
	password    string
	db          int
	maxIdle     int
	maxActive   int
	idleTimeout int
}

func (r *redisLayer) do(cmd string, args ...interface{}) (interface{}, error) {
	conn := r.rdb.Get()
	defer conn.Close()
	if res, err := conn.Do(cmd, args...); err != nil {
		log.Criticalf("Redis Error :[%s].", err.Error())
		return nil, err
	} else {
		return res, err
	}
}

func (r *redisLayer) batch(cmd string, args ...interface{}) (interface{}, error) {
	conn := r.rdb.Get()
	defer conn.Close()
	//conn.Send("SELECT", r.db)
	conn.Send(cmd, args...)
	conn.Flush()
	//conn.Receive()
	if res, err := conn.Receive(); err != nil {
		log.Criticalf("Redis Error :[%s].", err.Error())
		return nil, err
	} else {
		return res, err
	}
}

func (r *redisLayer) Set(key string, value interface{}, options ...interface{}) bool {
	var res bool
	switch len(options) {
	case 0: //正常
		res, _ = redis.Bool(r.do("SET", key, value))
	case 1: //特殊处理
		arg := options[0]
		if mr.IsInstance(arg, []reflect.Kind{reflect.Int, reflect.Int32, reflect.Int64}) {
			res, _ = redis.Bool(r.do("SET", key, value, "EX", arg))
		} else if mr.IsInstance(arg, []reflect.Kind{reflect.Map}) {
			inputArgs := []interface{}{key, value}
			argMap := arg.(map[string]interface{})
			alreadyEX := false
			if _, ok := argMap["EX"]; ok {
				inputArgs = append(inputArgs, "EX")
				inputArgs = append(inputArgs, argMap["EX"])
				alreadyEX = true
			}
			if _, ok := argMap["PX"]; ok && !alreadyEX {
				inputArgs = append(inputArgs, "PX")
				inputArgs = append(inputArgs, argMap["PX"])
			}
			alreadyNX := false
			if _, ok := argMap["NX"]; ok {
				if argMap["NX"].(bool) {
					inputArgs = append(inputArgs, "NX")
					alreadyNX = true
				}
			}
			if _, ok := argMap["XX"]; ok && !alreadyNX {
				if argMap["XX"].(bool) {
					inputArgs = append(inputArgs, "XX")
				}
			}
			res, _ = redis.Bool(r.do("SET", inputArgs...))
		} else {
			panic(fmt.Sprintf("Wrong Type Of Argument [%s].", arg))
		}
	default:
		panic("Wrong Arguments Number.")
	}
	return res
}

func (r *redisLayer) Get(key string) interface{} {
	res, _ := r.do("GET", key)
	return res
}

func (r *redisLayer) Exists(key string) bool {
	res, _ := redis.Bool(r.do("EXISTS", key))
	return res
}

func (r *redisLayer) Expired(key string, seconds int) bool {
	res, _ := redis.Bool(r.do("EXPIRE", key, seconds))
	return res
}

func (r *redisLayer) Test() {
	r.Set("testing", 1, 10)
}

func Initial(server string, password string, db int, maxIdle int, maxActive int, idleTimeout int) *redis.Pool {
	redisPool := &redis.Pool{
		MaxIdle:     maxIdle,
		MaxActive:   maxActive,
		Wait:        true,
		IdleTimeout: time.Duration(idleTimeout) * time.Second,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", server)
			if err != nil {
				return nil, err
			}
			if password != "" {
				if _, err := c.Do("AUTH", password); err != nil {
					c.Close()
					return nil, err
				}
			}
			c.Do("SELECT", db)
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if time.Since(t) < time.Minute {
				return nil
			}
			_, err := c.Do("PING")
			return err
		},
	}
	return redisPool
}

func (r *redisLayer) Connect(configs map[string]string) {
	r.server = configs["redis_server"]
	r.password = configs["redis_password"]
	r.db = utils.ParseInt(maps.GetKeyWithDefaultS(configs, "default_db", "0"))
	r.maxIdle = utils.ParseInt(maps.GetKeyWithDefaultS(configs, "maxIdle", "3"))
	r.maxActive = utils.ParseInt(maps.GetKeyWithDefaultS(configs, "maxActive", "5"))
	r.idleTimeout = utils.ParseInt(maps.GetKeyWithDefaultS(configs, "idleTimeout", "180"))
	r.rdb = Initial(r.server, r.password, r.db, r.maxIdle, r.maxActive, r.idleTimeout)
	r.Test()
}

func (r *redisLayer) GetInstants() interface{} {
	return r.rdb
}

func init() {
	X.RegisterComponent(constants.ImplNoSqlKv, redisLayer{})
}
